﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Threading;

namespace MefBasic.Threading
{
    public class Job 
    {
        private int _maxWaitTimeToStop;
        public Action<Job> JobAction { get; private set; }
        public Action<JobProgressArgs> ProgressNotifyAction { get; private set; }
        public event EventHandler JobCompleted;
        public event CancelEventHandler StartingJob;
        protected List<string> DependentJobs { get; set; }
        protected void InvokeStartingJob(CancelEventArgs e)
        {
            if (StartingJob != null) 
                StartingJob(this, e);
        }

        public bool IsThreadSafe { get; set; }
        public JobStore Store { get; set; }
        public string Id { get; set; }
        public AutoResetEvent WaitEvent { private set; get; }

        public Job(Action<Job> jobAction)
            : this(jobAction, null)
        {
            MaxWaitTimeToStop = 5000;
        }
        public Job Dependency(params string [] dependentJobs)
        {
            DependentJobs.AddRange(dependentJobs);
            return this;
        }
        public Job(Action<Job> jobAction, Action<JobProgressArgs> progressNotifyAction)
        {
            ProgressNotifyAction = progressNotifyAction;
            JobAction = jobAction;
            Id = Guid.NewGuid().ToString();
            WaitEvent = new AutoResetEvent(false);
            Store = new JobStore {Status = JobStatus.NotStarted};
            ChildreenJob = new List<Job>();
            IsThreadSafe = true;
            DependentJobs=new List<string>();
        }

        
        protected void OnJobCompleted(EventArgs e)
        {
            switch (Store.Status)
            {
                case JobStatus.AbortRequested:
                    Store.Status = JobStatus.Aborted;
                    break;
                case JobStatus.Started:
                    Store.Status = JobStatus.Completed;
                    break;
            }
            IsComplete = true;
            if (JobCompleted != null)
                JobCompleted(this, e);
        }
        
        protected void OnProgressAction(JobProgressArgs args)
        {
            if (ProgressNotifyAction != null)
            {
                ProgressNotifyAction(args);
            }
        }

        public virtual void Start()
        {
            if(InvokeStartingJob())
                return;
            DoStart();
        }

        public void Resume()
        {
            if (Store.PauseRequested)
            {
                Store.PauseRequested = false;
                Store.Status = JobStatus.Started;
                return;
            }
        }

        protected void DoStart()
        {
            Store.Status = JobStatus.Started;
            WaitEvent.Reset();
            if (IsThreadSafe)
            {
                
                var worker = new BackgroundWorker();
                worker.DoWork += (s, e) => DoWork(e.Argument);
                worker.RunWorkerCompleted += (s, e) =>
                                                  {
                                                      WaitEvent.Set();
                                                      OnJobCompleted(new EventArgs());
                                                  };
                worker.RunWorkerAsync(this);
            }
            else
            {
                DoWork(this);
                WaitEvent.Set();
                OnJobCompleted(new EventArgs());
            }
        }

        protected bool InvokeStartingJob()
        {
            var cancelEventArgs = new CancelEventArgs(false);
            InvokeStartingJob(cancelEventArgs);
            var isCancelled = cancelEventArgs.Cancel;
            if(isCancelled)
            {
                Store.Status = JobStatus.Aborted;
                WaitEvent.Set();
                OnJobCompleted(new EventArgs());
            }
            return isCancelled;
        }

        private void DoWork(object args)
        {
            WaitForDependentJobs();
            var job = (Job) args;
            if (ChildreenJob.Count > 0)
            {
                foreach (var childJob in ChildreenJob)
                {
                    childJob.Start();
                }
                ChildreenJob.ForEach(c=>c.Wait());
            }
            if(JobAction!=null)
                JobAction(job);
        }

        protected void WaitForDependentJobs()
        {
            foreach (var dependentJob in DependentJobs)
            {
                Parent.GetChild(dependentJob).Wait();
            }
        }

        public bool IsComplete { get; protected set; }
        public void Wait()
        {
            if (IsComplete) return;
            if (IsComplete == false)
            {
                WaitEvent.WaitOne();
                WaitEvent.Set();
            }
        }

        protected List<Job> ChildreenJob { get; set; }

        public void AddChild(Job childJob)
        {
            childJob.Parent = this;
            ChildreenJob.Add(childJob);
        }

        public Job Parent { get; set; }

        public Job Root
        {
            get
            {
                return Parent == null ? this : Parent.Root;
            }
        }

        public int MaxWaitTimeToStop
        {
            get { return _maxWaitTimeToStop; }
            set { _maxWaitTimeToStop = value; }
        }

        public IEnumerable<Job> GetChildreen()
        {
            return ChildreenJob;
        }

        public void NotifyProgress()
        {
            OnProgressAction(new JobProgressArgs { Sender = this });
        }

        public void Abort()
        {
            Store.Status = JobStatus.AbortRequested;
            ChildreenJob.ForEach(c=>c.Abort());
        }

        public Job GetChild(string childId)
        {
            return GetChildreen().FirstOrDefault(child => child.Id == childId);
        }

        public void NotifyProgress(int progress)
        {
            OnProgressAction(new JobProgressArgs { Sender = this,Progress = progress});
        }

        public void StartBlocked()
        {
            var worker = new BackgroundWorkerEx();
            worker.DoWork += (s, e) =>
                                 {
                                     Start();
                                     Wait();
                                 };
            worker.RunWorkerBlocked();
        }

        public Job GetSibling(string id)
        {
            return Parent.GetChild(id);
        }

        public void Stop()
        {
            Store.StopRequested = true;
            var stopWatch = new Stopwatch();
            stopWatch.Start();
            while (!IsComplete&&stopWatch.ElapsedMilliseconds<MaxWaitTimeToStop)
            {
                DoEvents();
            }
        }
        private static void DoEvents()
        {
            Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate { }));
        }

        public void Pause()
        {
            Store.PauseRequested = true;
            var stopWatch = new Stopwatch();
            stopWatch.Start();
            while (Store.Status!=JobStatus.Paused && stopWatch.ElapsedMilliseconds < MaxWaitTimeToStop)
            {
                DoEvents();
            }
        }
    }
}